import json
import os
import logging
from datetime import datetime, timedelta, tzinfo
import threading
from typing import Union

import pytz


class Blacklist:
    def __init__(self, file_name='blacklist.txt', file_path=os.path.dirname(__file__), timezone : Union[str, tzinfo] = "Asia/Shanghai", list_type = 'json', block_time='1M'):
        self.file_name = file_name
        self.file_path = file_path
        if isinstance(timezone, str):
            timezone = pytz.timezone(timezone)
        elif not isinstance(timezone, tzinfo):
            raise TypeError("timezone must be a string or datetime.tzinfo")
        self.timezone = timezone
        self.list_type = list_type
        self.block_time = block_time
        self.blacklist = []
        self.whitelist = []
        self.block_time_Delta = self.parse_block_time()
        self.lock = threading.RLock()
        self._last_index = -1


    def __str__(self):
        def preview_list(name, lst, max_items=5):
            preview = lst[:max_items]
            more = f"... (+{len(lst) - max_items} more)" if len(lst) > max_items else ""
            return f"{name} [{len(lst)} items]: {preview} {more}".strip()
        return (
            f"<{self.__class__.__name__}>\n"
            f"  File name     : {self.file_name}\n"
            f"  File path     : {self.file_path}\n"
            # f"  Timezone      : {self.timezone.zone if hasattr(self.timezone, 'zone') else self.timezone}\n"
            # f"  List type     : {self.list_type}\n"
            # f"  Block time    : {self.block_time}\n"
            f"  Block delta   : {self.block_time_Delta}\n"
            f"  {preview_list('Blacklist', self.blacklist)}\n"
            f"  {preview_list('Whitelist', self.whitelist)}"
        )

    def parse_block_time(self):
        """
        解析 block_time 格式，返回 timedelta 对象。
        """
        time_map = {'Y': 'years', 'M': 'months', 'D': 'days', 'h': 'hours', 'm': 'minutes', 's': 'seconds'}
        time_values = {'years': 0, 'months': 0, 'days': 0, 'hours': 0, 'minutes': 0, 'seconds': 0}
        num = ''
        for char in self.block_time:
            if char.isdigit():
                num += char
            elif char in time_map:
                if num:
                    key = time_map[char]
                    time_values[key] += int(num)
                    num = ''
        # 将 years 和 months 转换为 days
        days = time_values['years'] * 365 + time_values['months'] * 30 + time_values['days']
        return timedelta(days=days, hours=time_values['hours'], minutes=time_values['minutes'],
                         seconds=time_values['seconds'])

    def is_blocked(self, item):
        """
        Check if an item is in the blacklist.
        Returns True if the item is blocked, False otherwise.
        """
        with self.lock:
            if self.blacklist is None:
                logging.error("Blacklist not loaded.")
                return False
            self.update()  # Ensure blacklist is up-to-date
            # Use a set for faster lookup if blacklist is a list of dictionaries
            blacklist_items = {entry.get("item") for entry in self.blacklist}
            return item in blacklist_items

    def get_unblocked_items(self, item_list: list):
        """
        从传入的 item_list 中筛选出未被黑名单阻止的项。
        返回一个新的列表，包含所有未被阻止的项。
        """
        with self.lock:
            self.update()  # 确保黑名单是最新的
            blacklist_items = {entry.get("item") for entry in self.blacklist}
            return [item for item in item_list if item not in blacklist_items]


    def set_blacklist(self, blacklist: list):
        """
        根据传入的 blacklist 和 block_time 约束，更新 self.blacklist 和生成 whitelist。
        """
        with self.lock:
            now = datetime.now(self.timezone)

            self.whitelist = []
            self.blacklist = []

            for entry in blacklist:
                # add_time = datetime.strptime(entry['add_time'], '%Y-%m-%d %H:%M:%S')
                add_time = self.timezone.localize(datetime.strptime(entry['add_time'], '%Y-%m-%d %H:%M:%S'))
                if now - add_time > self.block_time_Delta:
                    self.whitelist.append(entry)
                else:
                    self.blacklist.append(entry)

            logging.info(f"Whitelist: {self.whitelist}")
            logging.info(f"Blacklist: {self.blacklist}")

    def block_item(self, item):
        """
        将 item 加入黑名单。
        如果 item 已存在于 whitelist 或 blacklist 中，则更新其时间。
        """
        with self.lock:
            now = datetime.now(self.timezone)
            entry = {
                'item': item,
                'add_time': now.strftime('%Y-%m-%d %H:%M:%S')
            }

            # 检查并更新 whitelist
            for whitelist_entry in self.whitelist:
                if whitelist_entry['item'] == item:
                    self.whitelist.remove(whitelist_entry)
                    break

            # 检查并更新 blacklist
            for blacklist_entry in self.blacklist:
                if blacklist_entry['item'] == item:
                    self.blacklist.remove(blacklist_entry)
                    break

            # 添加到 blacklist
            self.blacklist.append(entry)
            logging.info(f"Item {item} added to blacklist.")

    def save(self):
        """
        Save the blacklist to a file.
        """
        with self.lock:
            if not os.path.exists(self.file_path):
                os.makedirs(self.file_path, exist_ok=True)
            file_full_path = os.path.join(self.file_path, self.file_name)
            with open(file_full_path, 'w', encoding='utf-8') as f:
                if self.list_type == 'json':
                    entitylist = self.whitelist + self.blacklist
                    json.dump(entitylist, f, ensure_ascii=False, indent=4)
                elif self.list_type == 'xml':
                    logging.error("XML saving not implemented yet.")
                elif self.list_type == 'yml':
                    logging.error("YAML saving not implemented yet.")
                else:
                    logging.error(f"Unsupported list type: {self.list_type}. Supported types are: json, xml, yml.")
    _instance_pool = {}
    _instance_lock = threading.Lock()
    @classmethod
    def load_blacklist(cls, file_name='blacklist.txt', file_path=os.path.dirname(__file__),
                       timezone : Union[str, tzinfo] = "Asia/Shanghai",
                       list_type = 'json', block_time='1M'):
        """
        Load the blacklist from a file.
        Returns a set of blacklisted words.
        """
        file_full_path = os.path.join(os.path.dirname(__file__), file_name)
        with cls._instance_lock:
            if file_full_path in cls._instance_pool:
                return cls._instance_pool[file_full_path]

        blacklist = cls(file_name, file_path, timezone, list_type, block_time)
        # blacklist = Blacklist(file_name, file_path, timezone, list_type, block_time)
        if not os.path.exists(file_full_path):
            logging.error(f"Blacklist file {file_full_path} does not exist.")
            return blacklist
        with open(file_full_path, 'r', encoding='utf-8') as f:
            if list_type == 'json':
                blacklist.set_blacklist(json.load(f))
                # blacklist.blacklist = json.load(f)
            elif list_type == 'xml':
                logging.error("XML parsing not implemented yet.")
                return blacklist
            elif list_type == 'yml':
                logging.error("YAML parsing not implemented yet.")
                return blacklist
            else:
                logging.error(f"Unsupported list type: {list_type}. Supported types are: json, xml, yml.")
                return blacklist
        cls._instance_pool[file_full_path] = blacklist
        return blacklist

    def update(self, now = None):
        with self.lock:
            if now is None:
                now = datetime.now(tz = self.timezone)
            blacklist = []
            for entry in self.blacklist:
                add_time = self.timezone.localize(datetime.strptime(entry['add_time'], '%Y-%m-%d %H:%M:%S'))
                if now - add_time > self.block_time_Delta:
                    self.whitelist.append(entry)
                else:
                    blacklist.append(entry)
            self.blacklist = blacklist
            logging.info(f"Whitelist: {self.whitelist}")
            logging.info(f"Blacklist: {self.blacklist}")

    def unblock_item(self, item):
        """
        将 item 从黑名单和白名单中移除。
        """
        with self.lock:
            # 检查并移除黑名单中的 item
            for entry in self.blacklist:
                if entry['item'] == item:
                    self.blacklist.remove(entry)
                    logging.info(f"Item {item} removed from blacklist.")
                    break

            # 检查并移除白名单中的 item
            for entry in self.whitelist:
                if entry['item'] == item:
                    self.whitelist.remove(entry)
                    logging.info(f"Item {item} removed from whitelist.")
                    break

    def block_items(self, items:list):
        """
        将多个 items 加入黑名单。
        """
        with self.lock:
            for item in items:
                self.block_item(item)

    def unblock_items(self, items:list):
        """
        将多个 items 从黑名单和白名单中移除。
        """
        with self.lock:
            for item in items:
                self.unblock_item(item)

    def get_next_unblocked_item(self, item_list: list, strategy: str = "linear"):
        """
        从 item_list 中返回第一个未被黑名单阻止的项，线程安全。
        如果全部被阻止，返回 None。
        """
        with self.lock:
            self.update()
            blacklist_items = {entry.get("item") for entry in self.blacklist}
            available = [item for item in item_list if item not in blacklist_items]
        if not available:
            return None

        if strategy == "random":
            import random
            return random.choice(available)

        elif strategy == "linear":
            return available[0]

        elif strategy == "next":
            if not hasattr(self, "_last_index"):
                self._last_index = -1
            for i in range(self._last_index + 1, len(item_list)):
                if item_list[i] not in blacklist_items:
                    self._last_index = i
                    return item_list[i]
            return None

        elif strategy == "round":
            if not hasattr(self, "_last_index"):
                self._last_index = -1
            total = len(item_list)
            for offset in range(1, total + 1):
                i = (self._last_index + offset) % total
                if item_list[i] not in blacklist_items:
                    self._last_index = i
                    return item_list[i]
            return None

        else:
            raise ValueError(f"Unsupported strategy: {strategy}")





